package gpstools.data;

import generated.gpx.ExtensionsType;
import generated.gpx.GpxType;
import generated.gpx.ObjectFactory;
import generated.gpx.TrackPointExtensionT;
import generated.gpx.TrkType;
import generated.gpx.TrksegType;
import generated.gpx.WptType;
import gpstools.Location;
import gpstools.TrainingPoint;
import gpstools.Waypoint;
import gpstools.track.Lap;
import gpstools.track.Track;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class GpxExporter implements GpsDataExporter {
    private static final String DEFAULT_CREATOR = "http://code.google.com/p/gps-tools/";
    private static final int FRACTION_DIGITS = 14;

    private static WptType prepareGpxPoint(TrainingPoint point) {
        WptType gpxPoint = new WptType();
        gpxPoint.setLat(toBigDecimal(point.getLatitude()));
        gpxPoint.setLon(toBigDecimal(point.getLongitude()));
        gpxPoint.setEle(toBigDecimal(point.getElevation()));
		if (point.getTime() != null) {
			gpxPoint.setTime(XMLUtils.toXMLGregorianCalendar(point.getTime()));
		}

        if (point.getCadence() != null || point.getHeartRate() != null) {
            // TrackPointExtensionT extension = new TrackPointExtensionT();
            TrackPointExtensionT extension = new ObjectFactory().createTrackPointExtensionT();
            if (point.getHeartRate() != null) {
                extension.setHr(point.getHeartRate().shortValue());
            }
            if (point.getCadence() != null) {
                extension.setCad(point.getCadence().shortValue());
            }

            ExtensionsType extensionsType = new ExtensionsType();
            extensionsType.getAny().add(new ObjectFactory().createTrackPointExtension(extension));

            gpxPoint.setExtensions(extensionsType);
        }

        return gpxPoint;
    }

    private static TrkType prepareGpxTrack(Track track) {
        TrkType gpxTrack = new TrkType();
        gpxTrack.setName(track.getName());
        for (Lap lap : track.getLaps()) {
            TrksegType gpxLap = new TrksegType();
            for (TrainingPoint point : lap.getPoints()) {
                WptType destinationPoint = prepareGpxPoint(point);
                gpxLap.getTrkpt().add(destinationPoint);
            }
            gpxTrack.getTrkseg().add(gpxLap);
        }
        return gpxTrack;
    }

    private static WptType prepareGpxWaypoint(Waypoint waypoint) {
        WptType gpxWaypoint = new WptType();
        gpxWaypoint.setName(waypoint.getLabel());
        gpxWaypoint.setLat(toBigDecimal(waypoint.getLatitude()));
        gpxWaypoint.setLon(toBigDecimal(waypoint.getLongitude()));
        return gpxWaypoint;
    }

    private static BigDecimal toBigDecimal(double latitude) {
        return BigDecimal.valueOf(latitude).setScale(FRACTION_DIGITS, RoundingMode.HALF_UP).stripTrailingZeros();
    }

    private String creator = DEFAULT_CREATOR;

    private List<Track> tracks = new ArrayList<Track>();

    private List<Waypoint> wayPoints = new ArrayList<Waypoint>();

    public GpxExporter() {
    }

    public GpxExporter(Track track) {
        add(track);
    }

    public GpxExporter add(Track track) {
        tracks.add(track);
        return this;
    }

    public GpxExporter add(Waypoint wayPoint) {
        wayPoints.add(wayPoint);
        return this;
    }

    public GpxExporter addWaypoint(Location position, String label) {
        return add(new Waypoint(position, label));
    }

    public String getCreator() {
        return creator;
    }

    private GpxType prepareGpxData() {
        // GpxType gpxType = new GpxType();
        GpxType gpxType = new ObjectFactory().createGpxType();
        gpxType.setCreator(creator);

        for (Track track : tracks) {
            TrkType gpxTrack = prepareGpxTrack(track);
            gpxType.getTrk().add(gpxTrack);
        }

        for (Waypoint waypoint : wayPoints) {
            gpxType.getWpt().add(prepareGpxWaypoint(waypoint));
        }

        return gpxType;
    }

    public GpxExporter setCreator(String creator) {
        this.creator = creator;
        return this;
    }

    public void store(OutputStream outputStream) {
        GpxType gpxType = prepareGpxData();
        try {
            JAXBContext jc = JAXBContext.newInstance(GpxType.class, TrackPointExtensionT.class);
            Marshaller m = jc.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            ObjectFactory of = new ObjectFactory();

            m.marshal(of.createGpx(gpxType), outputStream);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    public void store(String filename) throws IOException {
        FileOutputStream fos = new FileOutputStream(filename);
        store(fos);
        fos.close();
    }

}
